package com.cam.generator.service;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.cam.framework.system.common.dict.SysYesNoDict;
import com.cam.framework.utils.StringUtils;
import com.cam.generator.dict.GenCodeFieldTypeDict;
import com.cam.generator.dict.GenVersion;
import com.cam.generator.dto.GenCodeDTO;
import com.cam.generator.dto.GenCodeFieldDTO;
import com.cam.generator.entity.GenCode;
import com.cam.generator.mapStruct.GenCodeMapStruct;
import com.cam.generator.mapper.GenCodeMapper;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.StringWriter;
import java.nio.charset.Charset;
import java.util.*;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

@Service
@Transactional
public class GenCodeService extends ServiceImpl<GenCodeMapper, GenCode> {

    GenCodeMapStruct mapStruct;

    @Autowired
    private GenCodeFieldService fieldService;

    public GenCodeService(GenCodeMapStruct mapStruct) {
        this.mapStruct = mapStruct;
    }

    public GenCodeDTO findById(String id) {
        GenCode genCode = baseMapper.selectById(id);
        GenCodeDTO genCodeDTO = mapStruct.toDTO(genCode);
        if (genCodeDTO != null) {
            genCodeDTO.setFieldList(fieldService.findList(id));
        }
        return genCodeDTO;
    }

    public void saveOrUpdate(GenCodeDTO genCodeDTO) {
        GenCode genCode = mapStruct.toEntity(genCodeDTO);
        super.saveOrUpdate(genCode);
        genCodeDTO.setId(genCode.getId());

        this.saveFieldList(genCodeDTO);
    }

    public void saveFieldList(GenCodeDTO genCodeDTO) {
        List<GenCodeFieldDTO> fieldList = genCodeDTO.getFieldList();
        for (GenCodeFieldDTO eachField : fieldList) {
            if (StringUtils.isEmpty(eachField.getType())) {
                eachField.setType(GenCodeFieldTypeDict.TEXT.getValue());
            }
            if (StringUtils.isEmpty(eachField.getDbType())) {
                if (GenCodeFieldTypeDict.OBJECT == eachField.getTypeDict()) {
                    eachField.setDbField(StringUtils.CamelToUnderlineCase(eachField.getJavaField()) + "_id");
                } else {
                    eachField.setDbField(StringUtils.CamelToUnderlineCase(eachField.getJavaField()));
                }
                eachField.setDbType(eachField.getTypeDict().getDbType());
                if (eachField.getDbTypeDict().getLengthFlag()) {
                    eachField.setDbLength(eachField.getDbTypeDict().getLengthDefault());
                    eachField.setDbPrecision(eachField.getDbTypeDict().getPrecisionDefault());
                }
            }
        }
        fieldService.removeByGenId(genCodeDTO.getId());
        fieldService.saveOrUpdateList(genCodeDTO.getFieldList(), genCodeDTO.getId());
    }

    public List<String> prepareTableSqlFieldList(GenCodeDTO genCodeDTO) {
        List<String> tableSqlFieldList = new ArrayList<>();
        List<GenCodeFieldDTO> fieldList = genCodeDTO.getFieldList();
        for (GenCodeFieldDTO eachField : fieldList) {
            StringBuilder eachFieldSqlBuilder = new StringBuilder();
            eachFieldSqlBuilder.append("`").append(eachField.getDbField()).append("`");
            eachFieldSqlBuilder.append(" ").append(eachField.getDbTypeDict().getDbType());
            if (eachField.getDbTypeDict().getLengthFlag()) {
                eachFieldSqlBuilder.append("(");
                eachFieldSqlBuilder.append(eachField.getDbLength());
                if (eachField.getDbTypeDict().getPrecisionFlag()) {
                    eachFieldSqlBuilder.append(",").append(eachField.getDbPrecision());
                }
                eachFieldSqlBuilder.append(")");
            }
            if (eachField.isDbFieldTextType()) {
                eachFieldSqlBuilder.append(" ").append("CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci");
            }
            if (SysYesNoDict.YES.getValue().equals(eachField.getDbNotNullFlag())) {
                eachFieldSqlBuilder.append(" ").append("NOT NULL");
            } else {
                eachFieldSqlBuilder.append(" ").append("DEFAULT NULL");
            }
            eachFieldSqlBuilder.append(" ").append("COMMENT ").append("'").append(eachField.getLabel()).append("'");
            String eachFieldSql = eachFieldSqlBuilder.toString();
            tableSqlFieldList.add(eachFieldSql);
        }
        return tableSqlFieldList;
    }

    public HashSet<String> prepareEntityImportList(GenCodeDTO genCodeDTO) {
        // Set不允许重复的数据，故而不适用List
        HashSet<String> entityImportList = new HashSet<>();
        for (GenCodeFieldDTO eachField : genCodeDTO.getFieldList()) {
            if (GenCodeFieldTypeDict.DATE == eachField.getTypeDict() || GenCodeFieldTypeDict.DATETIME == eachField.getTypeDict()) {
                entityImportList.add("java.util.Date;");
            }
        }
        return entityImportList;
    }

    public HashSet<String> prepareDtoImportList(GenCodeDTO genCodeDTO) {
        // Set不允许重复的数据，故而不适用List
        HashSet<String> domainImportList = new HashSet<>();
        for (GenCodeFieldDTO eachField : genCodeDTO.getFieldList()) {
            if (GenCodeFieldTypeDict.DATE == eachField.getTypeDict() || GenCodeFieldTypeDict.DATETIME == eachField.getTypeDict()) {
                domainImportList.add("com.fasterxml.jackson.annotation.JsonFormat;");
                domainImportList.add("java.util.Date;");
            }
        }
        return domainImportList;
    }

    public VelocityContext prepareVelocityContext(GenCodeDTO genCodeDTO) {
        VelocityContext velocityContext = new VelocityContext();

        velocityContext.put("StringUtils", StringUtils.class);

        velocityContext.put("obj", genCodeDTO);
        velocityContext.put("packagePath", genCodeDTO.getPackagePath());
        velocityContext.put("tableName", genCodeDTO.getTableName());
        velocityContext.put("entityName", genCodeDTO.getEntityName());
        velocityContext.put("entityLCName", genCodeDTO.getEntityLCName());
        velocityContext.put("permission", genCodeDTO.getPermission());
        velocityContext.put("viewPath", genCodeDTO.getViewPath());
        velocityContext.put("fieldList", genCodeDTO.getFieldList());
        velocityContext.put("moduleName", genCodeDTO.getModuleName());
        velocityContext.put("requestPath", genCodeDTO.getViewPath());
        velocityContext.put("entityHyphenName", genCodeDTO.getEntityHyphenName());

        velocityContext.put("dtoImportList", this.prepareDtoImportList(genCodeDTO));
        velocityContext.put("entityImportList", this.prepareEntityImportList(genCodeDTO));
        velocityContext.put("tableSqlFieldList", this.prepareTableSqlFieldList(genCodeDTO));

        return velocityContext;
    }

    public List<Map<String, String>> getTemplateList(GenCodeDTO genCodeDTO, GenVersion genVersion) {
        String entityName = genCodeDTO.getEntityName();
        String javaCategory = "java/" + genCodeDTO.getPackagePath().replace(".", "/");
        String webCategory = "web/" + genCodeDTO.getViewPath();

        return genVersion.getTemplateList().stream().peek(eachTemplateMap -> {
            String categoryPath = eachTemplateMap.get("categoryPath");
            categoryPath = categoryPath.replace("{webCategory}", webCategory);
            categoryPath = categoryPath.replace("{javaCategory}", javaCategory);
            categoryPath = categoryPath.replace("{entityName}", entityName);
            eachTemplateMap.put("categoryPath", categoryPath);
        }).collect(Collectors.toList());
    }

    public List<Map<String, String>> getPreviewList(String id, GenVersion genVersion) {
        GenCodeDTO genCodeDTO = findById(id);
        List<Map<String, String>> result = new ArrayList<>();
        VelocityContext velocityContext = prepareVelocityContext(genCodeDTO);
        List<Map<String, String>> templateList = getTemplateList(genCodeDTO, genVersion);
        for (Map<String, String> eachTemplateMap : templateList) {
            StringWriter eachSW = new StringWriter();
            Template template = Velocity.getTemplate(eachTemplateMap.get("templatePath"), Charset.defaultCharset().name());
            template.merge(velocityContext, eachSW);
            result.add(new HashMap<String, String>(){{
                put("name", eachTemplateMap.get("categoryPath"));
                put("data", eachSW.toString());
            }});
        }
        return result;
    }

    public ByteArrayOutputStream getDownloadOutputStream(GenCodeDTO genCodeDTO, GenVersion genVersion) throws IOException {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        ZipOutputStream zipStream = new ZipOutputStream(outputStream);
        List<Map<String, String>> templateList = getTemplateList(genCodeDTO, genVersion);
        VelocityContext velocityContext = prepareVelocityContext(genCodeDTO);
        for (Map<String, String> eachTemplateMap : templateList) {
            StringWriter eachSW = new StringWriter();
            Template template = Velocity.getTemplate(eachTemplateMap.get("templatePath"), Charset.defaultCharset().name());
            template.merge(velocityContext, eachSW);

            zipStream.putNextEntry(new ZipEntry(eachTemplateMap.get("categoryPath")));
            zipStream.write(eachSW.toString().getBytes(Charset.defaultCharset()));
            zipStream.closeEntry();
        }
        zipStream.close(); // 会自动关闭内层流
        return outputStream;
    }

    public ByteArrayOutputStream getDownloadOutputStream(String id, GenVersion genVersion) throws IOException {
        GenCodeDTO genCodeDTO = findById(id);
        return getDownloadOutputStream(genCodeDTO, genVersion);
    }

    public List<Map<String, String>> getTemplateListForJeeplus8x(GenCodeDTO genCodeDTO){
        String entityName = genCodeDTO.getEntityName();
        String javaCategory = "java/" + genCodeDTO.getPackagePath().replace(".", "/");
        String webCategory = "web/" + genCodeDTO.getViewPath();
        List<Map<String, String>> templateList = new ArrayList<>();

        templateList.add(new HashMap<String, String>() {{
            put("categoryPath", String.format("%s/web/%sController.java", javaCategory, entityName));
            put("templatePath", "vms/jeeplus/8x/controller.vm");
        }});

        templateList.add(new HashMap<String, String>() {{
            put("categoryPath", String.format("%s/entity/%s.java", javaCategory, entityName));
            put("templatePath", "vms/jeeplus/8x/entity.vm");
        }});

        templateList.add(new HashMap<String, String>() {{
            put("categoryPath", String.format("%s/service/%sService.java", javaCategory, entityName));
            put("templatePath", "vms/jeeplus/8x/service.vm");
        }});

        templateList.add(new HashMap<String, String>() {{
            put("categoryPath", String.format("%s/mapper/%sMapper.java", javaCategory, entityName));
            put("templatePath", "vms/jeeplus/8x/mapper.vm");
        }});

        templateList.add(new HashMap<String, String>() {{
            put("categoryPath", String.format("%s/mapper/xml/%sMapper.xml", javaCategory, entityName));
            put("templatePath", "vms/jeeplus/8x/mapper.xml.vm");
        }});

        templateList.add(new HashMap<String, String>() {{
            put("categoryPath", "sql/table.sql");
            put("templatePath", "vms/jeeplus/9x/table.sql.vm");
        }});

        templateList.add(new HashMap<String, String>() {{
            put("categoryPath", "sql/menu.sql");
            put("templatePath", "vms/jeeplus/9x/menu.sql.vm");
        }});

        templateList.add(new HashMap<String, String>() {{
            put("categoryPath", String.format("%s/%sList.vue", webCategory, entityName));
            put("templatePath", "vms/jeeplus/8x/list.vm");
        }});

        templateList.add(new HashMap<String, String>() {{
            put("categoryPath", String.format("%s/%sForm.vue", webCategory, entityName));
            put("templatePath", "vms/jeeplus/8x/form.vm");
        }});

        return templateList;
    }

    public List<Map<String, String>> getTemplateListForJeeplus9x(GenCodeDTO genCodeDTO) {
        String entityName = genCodeDTO.getEntityName();
        String javaCategory = "java/" + genCodeDTO.getPackagePath().replace(".", "/");
        String webCategory = "web/" + genCodeDTO.getViewPath();
        List<Map<String, String>> templateList = new ArrayList<>();
        templateList.add(new HashMap<String, String>() {{
            put("categoryPath", String.format("%s/controller/%sController.java", javaCategory, entityName));
            put("templatePath", "vms/jeeplus/9x/controller.vm");
        }});
        templateList.add(new HashMap<String, String>() {{
            put("categoryPath", String.format("%s/domain/%s.java", javaCategory, entityName));
            put("templatePath", "vms/jeeplus/9x/entity.vm");
        }});
        templateList.add(new HashMap<String, String>() {{
            put("categoryPath", String.format("%s/service/%sService.java", javaCategory, entityName));
            put("templatePath", "vms/jeeplus/9x/service.vm");
        }});
        templateList.add(new HashMap<String, String>() {{
            put("categoryPath", String.format("%s/service/dto/%sDTO.java", javaCategory, entityName));
            put("templatePath", "vms/jeeplus/9x/dto.vm");
        }});
        templateList.add(new HashMap<String, String>() {{
            put("categoryPath", String.format("%s/service/mapstruct/%sWrapper.java", javaCategory, entityName));
            put("templatePath", "vms/jeeplus/9x/wrapper.vm");
        }});
        templateList.add(new HashMap<String, String>() {{
            put("categoryPath", String.format("%s/mapper/%sMapper.java", javaCategory, entityName));
            put("templatePath", "vms/jeeplus/9x/mapper.vm");
        }});
        templateList.add(new HashMap<String, String>() {{
            put("categoryPath", String.format("%s/mapper/xml/%sMapper.xml", javaCategory, entityName));
            put("templatePath", "vms/jeeplus/9x/mapper.xml.vm");
        }});
        templateList.add(new HashMap<String, String>() {{
            put("categoryPath", String.format("%s/%sList.vue", webCategory, entityName));
            put("templatePath", "vms/jeeplus/9x/list.vm");
        }});
        templateList.add(new HashMap<String, String>() {{
            put("categoryPath", String.format("%s/%sForm.vue", webCategory, entityName));
            put("templatePath", "vms/jeeplus/9x/form.vm");
        }});
        templateList.add(new HashMap<String, String>() {{
            put("categoryPath", String.format("%s/%sApi.js", webCategory, entityName));
            put("templatePath", "vms/jeeplus/9x/api.js.vm");
        }});
        templateList.add(new HashMap<String, String>() {{
            put("categoryPath", "sql/table.sql");
            put("templatePath", "vms/jeeplus/9x/table.sql.vm");
        }});
        templateList.add(new HashMap<String, String>() {{
            put("categoryPath", "sql/menu.sql");
            put("templatePath", "vms/jeeplus/9x/menu.sql.vm");
        }});
        return templateList;
    }

}
